/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/
const PENDING = 0 // 等待
const FULFILLED = 1 // 成功
const REJECTED = 2 // 失败

// 工具函数，在 then 方法中用于处理回调
function handleCallback(resolve, reject, callback, value, newPromise) {
  try {
    // 回调
    const ret = callback(value)
    // 判断返回值
    if (ret === newPromise) {
      // 避免 promise 返回自身
      reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    } else if (ret instanceof MyPromise) {
      // 等待 promise 完成
      ret.then(resolve, reject)
    } else {
      // 将值传给下一个 promise
      resolve(ret)
    }
  } catch (e) {
    // 将错误传给下一个 promise
    reject(e)
  }
}

/**
 * 模拟 Promise 实现
 */
class MyPromise {

  // executor 是一个立即执行的回调函数，称为执行器
  // 执行过程中如果抛出异常，promise 就失败
  constructor (executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }

  status = PENDING // 状态
  value = undefined // 成功的值
  reason = undefined // 失败的原因
  onFulfilled = [] // 待执行的成功回调列表
  onRejected = [] // 待执行的失败回调列表。两个列表只有一个被执行

  // 定义为箭头函数，使函数中的 this 指向 MyPromise 实例
  resolve = value => {
    if (this.status !== PENDING) return // 只有状态为等待，才可改变
    this.status = FULFILLED
    this.value = value
    this.onFulfilled.forEach(callback => callback())
    this.onFulfilled = []
  }

  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    this.onRejected.forEach(callback => callback())
    this.onRejected = []
  }

  // promise 的核心功能。检查当前 promise 状态，
  // 根据状态调用相应的回调，并返回新的 promise
  then (onFulfilled, onRejected) {
    // 处理默认值
    onFulfilled = onFulfilled ? onFulfilled : val => val
    onRejected = onRejected ? onRejected : err => {throw err}

    const newPromise = new MyPromise((resolve, reject) => {
      // 用于简化调用时的传参
      const handleResolve = _newPromise =>
        handleCallback(resolve, reject, onFulfilled, this.value, _newPromise)
      const handleReject = _newPromise =>
        handleCallback(resolve, reject, onRejected, this.reason, _newPromise)

      // 情况一、成功
      if (this.status == FULFILLED) {
        // 使用异步代码取得 newPromise 的值
        setTimeout(() => handleResolve(newPromise), 0)
      }
      // 情况二、失败
      else if (this.status == REJECTED) {
        setTimeout(() => handleReject(newPromise), 0)
      }
      // 情况三、等待
      else {
        // 将成功回调压入数组
        this.onFulfilled.push(() => {
          setTimeout(() => handleResolve(newPromise), 0)
        })
        // 将失败回调压入数组
        this.onRejected.push(() => {
          setTimeout(() => handleReject(newPromise), 0)
        })
      }
    })
    return newPromise
  }

  catch (onReject) {
    return this.then(undefined, onReject)
  }

  // 传入数组的元素可以是值或 promise
  // 返回的 promise 成功当且仅当传入的 promise 全部成功
  static all (arr) {
    const res = []
    let count = 0
    return new MyPromise((resolve, reject) => {
      const add = i => val => {
        res[i] = val
        // 所有异步操作完成，就调用 resolve
        if (++count === arr.length) resolve(res)
      }
      arr.forEach((val, i) => {
        if (val instanceof MyPromise) {
          // 某个 promise 失败，就调用 reject
          val.then(add(i), reject)
        } else {
          add(i)(val)
        }
      })
    })
  }

  // 若 val 是 promise 则直接返回
  // 否则包装为 promise 再返回
  static resolve (val) {
    if (val instanceof MyPromise) return val
    return new MyPromise(resolve => resolve(val))
  }

  static reject (val) {
    return new MyPromise((resolve, reject) => reject(val))
  }

  // 不管当前 promise 成功与否，
  // onFinally 回调都会执行并把值向后传递。
  // 特别当 onFinally 回调返回一个 promise 时，
  // 会等待这个 promise 完成再向下执行，
  // 但是这个 promise 的值会被丢弃
  finally (onFinally) {
    return this.then(
      value => MyPromise.resolve(onFinally()).then(() => value),
      reason => MyPromise.resolve(onFinally()).then(() => {throw reason})
    )
  }
}

function testPromise () {
  new MyPromise((resolve, reject) => {
    //resolve('成功')
    //reject('失败')
    setTimeout(() => reject('稍后失败'), 300)
    //setTimeout(() => resolve('稍后成功'), 300)
  }).then(console.log).catch(console.error)
}

function testCatch() {
  new MyPromise((resolve, reject) => {
    throw new Error('executor error')
    resolve(10)
  })
  .catch(err => console.error(err.message))
  .then(() => {throw new Error('then error')})
  .catch(err => console.error(err.message))
}

function testStaticAll () {
  const p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => resolve('p1'), 300)
  })
  const p2 = new MyPromise((resolve, reject) => {
    resolve('p2')
  })
  MyPromise.all(['a', 'b', p1, p2, 'c']).then(console.log)
}

function testStaticResolve () {
  const p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => resolve('hello'), 300)
  })
  MyPromise.resolve(10).then(console.log)
  MyPromise.resolve(p1).then(console.log)
}

function testFinally () {
  const p1 = new MyPromise((resolve, reject) => {
    //reject('reject')
    resolve('resolve')
  })
  const p2 = new MyPromise((resolve, reject) => {
    setTimeout(() => resolve('whatever'), 300) // whatever 不打印
  })
  p1.finally(() => {
    console.log('finally')
    return p2
  }).then(console.log, console.error)
}

module.exports = MyPromise
